package org.roger.valuesource;

import java.io.IOException;
import java.util.Map;

import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.FloatDocValues;
import org.apache.lucene.queries.function.valuesource.QueryValueSource;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.Bits;

public class SelfValueSource extends ValueSource{
	
	final Query query;
	final float defVal;
	
	public SelfValueSource(Query query,float defVal){
		this.query = query;
		this.defVal = defVal;
	}
	
	public Query getQuery(){ 
		return query; 
	}
	
	@Override
	public FunctionValues getValues(Map context,
			AtomicReaderContext readerContext) throws IOException {
		return new SelfQueryDocValues(this, readerContext, context);
	}

	@Override
	public boolean equals(Object o) {
		if (QueryValueSource.class != o.getClass()) 
			return false;
	    QueryValueSource other = (QueryValueSource)o;
	    return this.query.equals(other.getQuery()) && this.defVal==other.getDefaultValue();
	}
	
	 @Override
	 public void createWeight(Map context, IndexSearcher searcher) throws IOException {
	    Weight w = searcher.createNormalizedWeight(query);
	    context.put(this, w);
	 }

	@Override
	public int hashCode() {
		return query.hashCode() * 29;
	}

	@Override
	public String description() {
		 return "query(" + query + ",def=" + defVal + ")";
	}
}

class SelfQueryDocValues extends FloatDocValues {
	
	final AtomicReaderContext readerContext;
	final Bits acceptDocs;
	final float defVal;
	final Map fcontext;
	final Query query;
	final Weight weight;
	boolean noMatches=false;
	Scorer scorer;
	int scorerDoc; // the document the scorer is on
	
	int lastDocRequested=Integer.MAX_VALUE;

	public SelfQueryDocValues(SelfValueSource vs,AtomicReaderContext readerContext, Map fcontext) throws IOException {
		super(vs);
		this.readerContext = readerContext;
	    this.acceptDocs = readerContext.reader().getLiveDocs();
	    this.defVal = vs.defVal;
	    this.query = vs.query;
	    this.fcontext = fcontext;
	    
	    Weight w = fcontext==null ? null : (Weight)fcontext.get(vs);
	    if (w == null) {
	    	IndexSearcher weightSearcher;
	    	if(fcontext == null) {
	    		weightSearcher = new IndexSearcher(ReaderUtil.getTopLevelContext(readerContext));
	    	}else{
	    		weightSearcher = (IndexSearcher)fcontext.get("searcher");
	    		if (weightSearcher == null) {
	    			weightSearcher = new IndexSearcher(ReaderUtil.getTopLevelContext(readerContext));
	    		}
	    	}
	    	vs.createWeight(fcontext, weightSearcher);
	        w = (Weight)fcontext.get(vs);
	    }
	    weight = w;
	}

	@Override
	public float floatVal(int doc) {
		try{
			if (doc < lastDocRequested) {
				 if (noMatches)
					 return defVal;
				 scorer = weight.scorer(readerContext, acceptDocs);
				 if (scorer==null) {
			          noMatches = true;
			          return defVal;
			     }
				 scorerDoc = -1;
			}
			lastDocRequested = doc;
			if (scorerDoc < doc) {
		        scorerDoc = scorer.advance(doc);
		    }
			if (scorerDoc > doc) {
		        // query doesn't match this document... either because we hit the
		        // end, or because the next doc is after this doc.
		        return defVal;
		    }
			// a match!
		    return scorer.score();
		}catch(IOException e){
			throw new RuntimeException("caught exception in QueryDocVals("+query+") doc="+doc, e);
		}
	}
	
//	@Override
//	public boolean exists(int doc){
//		try{
//			if (doc < lastDocRequested) {
//				if(noMatches) 
//					return false;
//				scorer = weight.scorer(readerContext, acceptDocs);
//				scorerDoc = -1;
//		        if(scorer==null) {
//		        	noMatches = true;
//		        	return false;
//		        }
//	      }
//	      lastDocRequested = doc;
//
//	      if (scorerDoc < doc) {
//	        scorerDoc = scorer.advance(doc);
//	      }
//
//	      if (scorerDoc > doc) {
//	        // query doesn't match this document... either because we hit the
//	        // end, or because the next doc is after this doc.
//	        return false;
//	      }
//
//	      // a match!
//	      return true;
//	    } catch (IOException e) {
//	      throw new RuntimeException("caught exception in QueryDocVals("+query+") doc="+doc, e);
//	    }
//	}
	
//	 @Override
//	 public Object objectVal(int doc) {
//	     try {
//	    	 return exists(doc) ? scorer.score() : null;
//	     } catch (IOException e) {
//	    	 throw new RuntimeException("caught exception in QueryDocVals("+query+") doc="+doc, e);
//	     }
//	}
	 
//	 @Override
//	  public ValueFiller getValueFiller() {
//	    //
//	    // TODO: if we want to support more than one value-filler or a value-filler in conjunction with
//	    // the FunctionValues, then members like "scorer" should be per ValueFiller instance.
//	    // Or we can say that the user should just instantiate multiple FunctionValues.
//	    //
//		 System.out.println("*****************************");
//	    return new ValueFiller() {
//	      private final MutableValueFloat mval = new MutableValueFloat();
//
//	      @Override
//	      public MutableValue getValue() {
//	        return mval;
//	      }
//
//	      @Override
//	      public void fillValue(int doc) {
//	        try {
//	          if (noMatches) {
//	            mval.value = defVal;
//	            mval.exists = false;
//	            return;
//	          }
//	          scorer = weight.scorer(readerContext, acceptDocs);
//	          scorerDoc = -1;
//	          if (scorer==null) {
//	            noMatches = true;
//	            mval.value = defVal;
//	            mval.exists = false;
//	            return;
//	          }
//	          lastDocRequested = doc;
//
//	          if (scorerDoc < doc) {
//	            scorerDoc = scorer.advance(doc);
//	          }
//
//	          if (scorerDoc > doc) {
//	            // query doesn't match this document... either because we hit the
//	            // end, or because the next doc is after this doc.
//	            mval.value = defVal;
//	            mval.exists = false;
//	            return;
//	          }
//
//	          // a match!
//	          mval.value = scorer.score();
//	          mval.exists = true;
//	        } catch (IOException e) {
//	          throw new RuntimeException("caught exception in QueryDocVals("+query+") doc="+doc, e);
//	        }
//	      }
//	    };
//	  }
	 
	 
	@Override
	public String toString(int doc) {
	    return "query(" + query + ",def=" + defVal + ")=" + floatVal(doc);
	}
	
}
